Skip to content

js_parser: make --drop turn <dropped>.method.bind(...) into () => {}, not undefined - #45566

Open
robobun wants to merge 2 commits into
mainfrom
robobun/7d04d8c2/drop-keeps-bind-callable
Open

robobun wants to merge 2 commits into
mainfrom
robobun/7d04d8c2/drop-keeps-bind-callable

Conversation

@robobun

@robobun robobun commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • --drop=console turns const log = console.log.bind(console) into var log = undefined. The build succeeds, and log(...) throws TypeError: log is not a function.
  • e_call (src/js_parser/visit/visit_expr.rs:2115) replaces each call through a dropped name with undefined. Here that call is .bind(...), which only makes a function.

Fix

  • In the drop branch of e_call, a call of <dropped>.method.bind becomes () => {}. Each other dropped call stays undefined.
  • The .bind(...) arguments are removed, as for each dropped call. ASSERT.ok.bind(ASSERT) with --drop=ASSERT would throw ReferenceError.
  • The transpiler cache version is now 36, so a cached entry does not keep undefined.
  • Verified: test/bundler/bundler_drop.test.ts (9 new tests, 8 fail without the fix), test/cli/run/transpiler-cache.test.ts (1 new), bundler_minify, esbuild/dce. Self-reviewed: 11 concerns raised, 11 addressed.

Background

Downsides

  • No --drop: 0 instructions per property access, 0 to -1 per call, text +2,304 bytes. With --drop: +3 to +6 per dropped call, +1,054 per rewritten .bind.
  • A bound dropped method is now truthy, as in Terser and esbuild 0.25+: if (log) log(dump()) runs dump().
  • x = app.keys.bind(k, fn) with drop: ["app"] now gives a function, not undefined. Add app.keys.bind to the drop list to keep undefined.
Notes

Question for the reviewer. No maintainer has said what .bind gives under --drop. This PR says: a function that does nothing. #36875 and #40857 touch the same flag and can follow that rule. There are two shapes for it:

  • (a) this PR: one arm in e_call and a helper that reads the drop list again, then js_parser: set the import.meta.hot call-drop flag only for a call target #36875 for the flag.
  • (b) a typed request in P in place of the bool, which the code that sets the flag fills in. It removes the helper's second look at the callee and also fixes the --define case below. It changes each place that sets the flag.

Output

Input (--drop=console unless shown) main This PR
const log = console.log.bind(console) undefined () => {}
console.log.bind(console)("x") undefined("x"), throws (() => {})("x")
p.catch(console.error.bind(console)) p.catch(undefined) p.catch(() => {})
typeof console.log.bind(console) "undefined" "function"
console.log?.bind(console), console?.log.bind(console), console.log.bind?.(console) undefined () => {}
my.logger.bind(null) with --drop=my.logger undefined () => {}
ASSERT.ok.bind(ASSERT) with --drop=ASSERT undefined () => {}
console.log.bind(console); as a statement removed removed
console.log("x"), .call(...), .apply(...) undefined undefined
console.bind(x), Mousetrap.bind(k, fn) with --drop=Mousetrap undefined undefined
Mousetrap.bind(k, fn) with --drop=Mousetrap.bind undefined undefined
app.keys?.bind(k, fn) with --drop=app --drop=app.keys.bind undefined undefined
console.log.bind.call(console.log, console) undefined undefined
console["log"].bind(console) without --minify-syntax not dropped not dropped

A matrix of 54 shapes, with and without --minify-syntax, gives 46 different outputs. Each of them is a .bind on a dropped name. drop/BindOfEachDroppedCallee pins 22 callee spellings in both modes.

Why a bare arrow. esbuild prints (() => {}).bind(console) and Terser prints (function(){}).bind(). They keep the call because they handle each method name the same way. Bun treats only bind in a special way, so the value of the call is known. () => {} is callable, needs no pure annotation to go away when it is not used, and lets the typeof, ! and || folds see a function. The arguments are not kept, because --drop takes any name: ASSERT.ok.bind(ASSERT) with --drop=ASSERT would read a global that does not exist.

What a truthy value changes. Code that tests a bound console method now takes the other branch, as it does with no --drop: if (log) log(dump()) calls dump(), a library option { debug: console.log.bind(console) } counts as set, and a require() or import() behind such a test is in the bundle again. No callable value is falsy, so no output form avoids this. esbuild made the same change in 0.25.0.

Why the helper reads the drop list again. import.meta.hot.accept sets the same flag with no drop list when HMR is off (src/js_parser/fold.rs:765, :799), and a read of it with no call leaves the flag set for the next call (#36875). The helper gives () => {} only when the receiver is a dropped name. Without a drop list the output is the same as on main: 171 of 171 outputs are byte-identical (57 files, with no flag, --minify-syntax and --minify; the files include typescript.js, sass.dart.js and the import.meta.hot shapes).

Three conditions in the receiver walk copy the conditions of the code that sets the flag: the symbol is unbound, it is not in a with scope, and the chain ends in an identifier. After a drop key matched they are always true. They decide only after a stale import.meta.hot flag, where the output of main is wrong too, so no test pins them.

Why the helper has its own lookup loop. e_dot has the same loop over dots_for. It runs for each property access, and this PR does not change it. A const parameter on is_dot_define_match (to let one matcher read a?.b as a.b) changed the machine code of that matcher: +26 to +29 instructions for each dotted define lookup such as Object.keys or process.env.X. So the helper compares the names of the callee chain itself.

Measurements. Release builds of the merge base a407eae88a and of this PR (070a0a8440). bun build --no-bundle <file>, user-space instructions from perf_event_open, 5 interleaved runs, median. The base spread is 594 to 3,829 instructions.

Workload, no --drop main This PR Delta
1,000,000 property accesses, .js 1,608,189,606 1,608,188,969 -637
1,000,000 property accesses, .ts 1,655,694,635 1,655,694,671 +36
250,000 a.b(), .js 775,455,119 775,455,385 +266
250,000 a.b(), .ts 799,461,010 799,460,969 -41
250,000 a(), .js 588,371,127 588,371,305 +178
250,000 a(), .ts 612,876,687 612,876,545 -142
250,000 a.b.c.d.e() 1,348,674,661 1,348,675,244 +583
250,000 a.b(c, d) 1,363,407,583 1,363,157,305 -250,278
250,000 a(c) 883,181,194 882,931,517 -249,677
250,000 x = Object.keys(a) + Math.abs(b) 2,895,147,518 2,894,647,624 -499,894
250,000 x = process.env.NODE_ENV + process.env.FOO 2,042,075,148 2,042,075,237 +89
typescript.js (9.1 MB) 949,867,381 949,799,556 -67,825

The -1 per call with arguments is block layout: the new arm ends in a return.

Workload, --drop=console Delta Per statement
250,000 x = console.log.bind(console) +263,530,375 +1,054
250,000 console.log(1) +750,205 +3
250,000 console.warn(1) +1,500,055 +6
250,000 x = console.bind(1) +9,000,172 +36
typescript.js -67,718
  • visit_expr_in_out: 17,694 to 17,752 bytes (JS copy), 17,555 to 17,620 (TS copy). Prologue subq $0x248, %rsp in both builds.
  • The cold helper is 1,162 bytes in each copy. Release binary: text 106,032,565 to 106,034,869, data and bss do not change (size).
  • One 32-byte E::Arrow node for each rewritten .bind. No allocation on any other path.

The e_dot design of #33852, same procedure (relinks of a407eae88a): +6.0 instructions per property access, +8.0 per callee access, typescript.js +416,317 (+0.044%), visit_expr_in_out +192 bytes.

The shape in e_call matters. A call to the cold helper from the block that writes undefined made LLVM inline handle_react_refresh_hook_call and transpose_known_to_be_if_require into visit_expr_in_out: +992 bytes and a frame of 0x258. The guard arm ahead of the argument visit does not change those decisions.

Tests. On a release build of the merge base, 8 of the 9 new tests in bundler_drop.test.ts fail, and so does the new test in transpiler-cache.test.ts. drop/BindKeepsPlainCallsDropped is a guard: it passes on main and pins the calls that stay undefined. Builds with one condition of the helper removed fail it or the table: the member receiver, the callee on the drop list, the dropped receiver name, and the visit of the arguments (panic: Scope mismatch while visiting). The single-use inlining pass visits the call a second time; the viaAlias and viaLocal lines of drop/BindStaysCallable* cover it.

Other suites. test/bundler/bundler_edgecase.test.ts: 181 pass. edgecase/DeepImportDiamondDAG timed out once in the full run of the debug build and passes alone (46 s).

History. #33852 was the first attempt. The stale workflow (close-stale-robobun-prs.yml) closed it after 90 days. Its only reviews were from bots, and no maintainer rejected it.

Not changed here

  • --drop=X.m with a --define that renames X: with --drop=global.console --define global=globalThis, global.console.log.bind(global.console) is still undefined. The helper reads the visited callee, and the define already renamed its root. Shape (b) above fixes this.
  • import.meta.hot.accept.bind(x) with HMR off stays undefined.
  • ASSERT.bind(null) with --drop=ASSERT stays undefined: it can not be told from Mousetrap.bind(k, fn) with --drop=Mousetrap.
  • console.log.toString() and other calls on a dropped method stay undefined. drop/BecomesUndefinedNested2 pins the drop at any depth.
  • console["log"].bind(console) without --minify-syntax is not dropped, as on main.

Related. #36875 changes how the same flag is set, on the same lines of e_call, and also takes cache version 36. The PR that lands second takes 37, and only the read of the flag in e_call changes here. If #40857 makes e_index forward the drop request, the receiver walk in the helper needs an EIndex level, and the table row for debug[key] shows it.


no test proof · iteration 3 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/cli/run/transpiler-cache.test.ts

…ction

`--drop=console` replaces each call through a dropped name with
`undefined`. `console.log.bind(console)` is such a call, but it does not
call `console.log`. It makes a function that the program calls later.
The output was `var log = undefined`, and the built program threw
`TypeError: log is not a function` at load.

In the drop branch of `e_call`, a call of `<dropped>.method.bind` now
becomes `() => {}`. Its arguments are removed, like the arguments of
each dropped call. Each other dropped call stays `undefined`:
`.call`, `.apply`, a method that is named `bind` on the dropped name
itself (`Mousetrap.bind(key, fn)` with `--drop=Mousetrap`), and a drop
key that ends in `.bind`.

The helper reads the drop list again and does not trust the flag alone,
because `import.meta.hot` sets the same flag with no drop list.

The runtime transpiler cache version is now 36, so that a cached entry
does not keep the old output.
… callee spelling

With `--drop=app --drop=app.keys.bind`, `app.keys?.bind(k, fn)` became
`() => {}`. The check for a callee that is itself on the drop list used
`is_dot_define_match`, which does not match through `?.`. The check now
compares the names of the callee chain with the drop key, so each
optional spelling stays `undefined`.

The guard arm in `e_call` tests the name `bind`, so the helper does not
test it a second time.

Tests: one table with a row for each callee spelling, the truthiness of
a bound dropped method, the index spellings, the single-use inlining
pass, arguments that contain scopes, and a drop key that ends in
`.bind` with its prefix on the drop list too.
@robobun

robobun commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 1:05 PM PT - Oct 16th, 2026

❌ @robobun, your commit 070a0a8 has 1 failures in Build #126798 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 45566

That installs a local version of the PR into your bun-45566 executable, so you can run:

bun-45566 --bun

@robobun

robobun commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator Author

Status

Reproduced on stock 1.4.2 and on release builds of 367d939d9 (1.4.3-canary) and of main (a407eae88a):

// in.mjs
const log = console.log.bind(console);
log("hello");
process.stdout.write("ALIVE\n");
  • bun build --drop=console in.mjs prints var log = undefined; log("hello");. The built file exits 1 with TypeError: log is not a function.
  • bun --drop=console in.mjs exits 1 with TypeError: (void 0) is not a function.

With this PR the output is var log = () => {};, and the program prints ALIVE.

Run the tests with bun bd test test/bundler/bundler_drop.test.ts and bun bd test test/cli/run/transpiler-cache.test.ts.

@coderabbitai

coderabbitai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: oven-sh/bun/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 6f3aabb2-4564-4551-a1b1-2517eec20304
📥 Commits

Reviewing files that changed from the base of the PR and between 5866b5f and 070a0a8.

📒 Files selected for processing: 6
  • docs/bundler/index.mdx
  • docs/bundler/minifier.mdx
  • src/js_parser/visit/visit_expr.rs
  • src/jsc/RuntimeTranspilerCache.rs
  • test/bundler/bundler_drop.test.ts
  • test/cli/run/transpiler-cache.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 1 remain after this review.

Walkthrough

The drop transform now replaces eligible .bind() calls on dropped functions with callable no-op functions. New tests cover bundler configurations and transpiler cache reuse. The bundler documentation describes the behavior.

Changes

Dropped-function bind handling

Layer / File(s) Summary
Recognize and lower dropped-function binds
src/js_parser/visit/visit_expr.rs, src/jsc/RuntimeTranspilerCache.rs
The parser recognizes dropped identifiers and dotted expressions, replaces eligible .bind() calls with no-op arrow functions, and increments the transpiler cache version.
Document and test bound-function behavior
test/bundler/bundler_drop.test.ts, test/cli/run/transpiler-cache.test.ts, docs/bundler/index.mdx, docs/bundler/minifier.mdx
Bundler tests cover bound functions, dropped-call arguments, and callee spellings. A CLI test covers cache reuse. The documentation explains that the bound result remains callable.

Suggested reviewers: jarred-sumner

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to 070a0

The bound-function behavior and cache version change are ready to merge after normal checks.

Pre-merge checks: Passed 4
Check name Status Explanation
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Title check Passed The title clearly identifies the main change: --drop now lowers dropped .bind(...) calls to a callable no-op instead of undefined.
Description check Passed The description explains the problem, implementation, behavior, scope, cache update, and verification results. It does not use the exact template headings, but it provides the required information and…
Full details: Description check

Explanation

The description explains the problem, implementation, behavior, scope, cache update, and verification results. It does not use the exact template headings, but it provides the required information and is substantially complete.

✨ Finishing Touches: 💡 1
🛠️ Fix failing CI checks: 💡
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

Beyond the inline findings, I also checked two things and ruled them out: the early-return arm in e_call restores ignore_dce_annotations and should_fold_typescript_constant_expressions the same way the fallthrough path does, and the helper saves/restores is_control_flow_dead itself before that flag is set; and the behavior change where a guarded bound logger (if (log) log(dump())) now takes the truthy branch is intentional per the documented downside and matches esbuild 0.25+, not a defect in the arm.

Extended reasoning...

The change adds a .bind-on-dropped-callee arm to the --drop handling in src/js_parser/visit/visit_expr.rs with three new lookup helpers, bumps the transpiler cache EXPECTED_VERSION to 36, and adds bundler and transpiler-cache tests plus docs; it touches no security-sensitive surface. Inline findings cover gaps in the receiver guard (X.bind vs deeper .bind members), the EIndex string-key mismatch between is_drop_key and is_dot_define_match, and two test nits, so a human should weigh those before merge.

Comment on lines +1883 to +1884
// Not a bind of a dropped function: `Mousetrap.bind(k, f)`, or a callee on the drop list.
if !matches!(bind.target.data, Data::EDot(..))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 (optional) Users who drop a namespace whose API has a method literally named bind now get a bare () => {} where the base gave undefined, so guards like if (h) h.unbind() enter and throw. src/js_parser/visit/visit_expr.rs:1884 only rejects a direct X.bind receiver; app.keys.bind(k, fn) with --drop=app passes because app.keys is an EDot and is_dropped_name matches app. Fix: rewrite to an arrow only when the receiver is a dropped callable method shape the user asked for (for example require the exact a.b.bind not be a plain method of a dropped namespace, or default to undefined when the bind call carries more than one argument), and apply the same rule at every sibling receiver shape.

Why this was flagged

Trigger: --drop=app (or --drop=$, --drop=Mousetrap-style namespace entries) and a call app.keys.bind("ctrl+s", fn) reached through e_call at src/js_parser/visit/visit_expr.rs:2212. The guard at src/js_parser/visit/visit_expr.rs:1884 only requires bind.target to be an EDot; is_drop_key at :1901 is false unless the user also listed app.keys.bind; is_dropped_name at :1930 returns true as soon as the root app is a drop entry. The helper then emits () => {} at :1897. On the base the same call reached :2245 and became undefined. Code that stored the result and guarded it, const h = app.keys.bind(k, fn); if (h) h.unbind(), skipped the branch on the base and now calls unbind on a bare arrow, throwing TypeError. The PR text names this as a downside but sizes it as rare; jQuery-style $.fn.bind and hotkey libraries make bind a common method name under a dropped namespace.

Verification: Trigger: --drop=<ns> where <ns> has a nested object whose own method is literally named bind and the call's return value is used. At src/js_parser/visit/visit_expr.rs:1884 bind.target is an EDot; is_drop_key (:1901) is false with only app dropped; is_dropped_name (:1924) returns true; the call is rewritten to E::Arrow::NOOP_RETURN_UNDEFINED at :1897. On the base the same input became undefined.

Comment on lines +1899 to +1924
}
/// Whether `callee` spells a drop key. Unlike `is_dot_define_match`, `a?.b` counts as `a.b`.
fn is_drop_key(p: &Self, callee: Expr) -> bool {
let Data::EDot(last) = callee.data else {
return false;
};
let Some(keys) = p.define.dots_for(last.name.slice()) else {
return false;
};
keys.as_slice().iter().any(|key| {
let Some((root, members)) = key.parts.split_first() else {
return false;
};
let mut expr = callee;
for member in members.iter().rev() {
match expr.data {
Data::EDot(dot) if strings::eql(member, &dot.name) => expr = dot.target,
_ => return false,
}
}
let Data::EIdentifier(id) = expr.data else {
return false;
};
key.data.method_call_must_be_replaced_with_undefined()
&& strings::eql(p.load_name_from_ref(id.ref_), root)
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 (optional) Users who list both a.b.c and a.b.c.bind in --drop still get () => {} instead of undefined when the call is spelled a["b"].c.bind(x). The flag is raised by is_dot_define_match (src/js_parser/p.rs:7280), which accepts a UTF-8 string EIndex as a dot part, but is_drop_key at src/js_parser/visit/visit_expr.rs:1911 only walks EDot nodes, so the full-callee drop key is missed and is_dropped_name then matches the a.b.c entry. Fix: make is_drop_key recognise every callee spelling that can raise the flag (string EIndex parts as well as EDot), e.g. by walking the same shapes is_dot_define_match walks, so the documented 'add <callee>.bind to the drop list to keep undefined' escape hatch holds for all spellings.

Why this was flagged

Input lib["log"].info.bind(x) built with drop: ["lib.log.info", "lib.log.info.bind"] and no minify-syntax, through bun build or Bun.build. Visiting the call target in e_dot (src/js_parser/visit/visit_expr.rs:1337-1357) matches lib.log.info.bind because is_dot_define_match treats lib["log"] as lib.log (src/js_parser/p.rs:7280-7291) and raises method_call_must_be_replaced_with_undefined. In e_call the new arm calls replace_bind_of_dropped_function; is_drop_key (src/js_parser/visit/visit_expr.rs:1899-1924) walks bind then info as EDot, reaches the EIndex lib["log"] and returns false, so the explicit lib.log.info.bind drop entry is not seen. is_dropped_name then matches lib.log.info via is_dot_define_match and the call is rewritten to () => {}. On the base branch the same input became undefined. The PR text says adding <callee>.bind to the drop list keeps undefined; that only holds for the pure dot spelling (and ?.), not for the bracket spelling the flag-setting matcher already accepts.

Verification: Trigger: --drop lists both a dropped name/method and its .bind spelling, the call uses bracket notation (lib["log"].info.bind(x)), and minify-syntax is off. is_drop_key (visit_expr.rs:1901-1925) walks the chain matching only Data::EDot, so the EIndex at lib["log"] makes it return false even though the full key is listed. Result: () => {} where base gives undefined.

Comment on lines +147 to +151
console?.log.bind(console)("FAIL");
console.log.bind?.(console)("FAIL");
["FAIL"].forEach(console.log.bind(console));
Promise.reject(new Error("FAIL")).catch(console.error.bind(console));
my.logger.bind(null)("FAIL");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 nit (optional): a maintainer gets a viaAlias check that prints "callable" with or without the fix, plus a comment that misdescribes the parser. m.logger.bind(null) at test/bundler/bundler_drop.test.ts:150 has a bound local m as its root, so e_identifier (src/js_parser/visit/visit_expr.rs:256) never sets the drop flag and the call is left as written. Single-use inlining (src/js_parser/visit/mod.rs:1963) swaps my in after the statement was visited; nothing is "visited again". Fix: either drop viaAlias or make it assert the negative contract it actually pins (an alias of a dropped name is not dropped), and reword the comment at test/bundler/bundler_drop.test.ts:143 so it does not claim a second visit.

Why this was flagged

The row out(viaAlias()) expects "callable" (test/bundler/bundler_drop.test.ts:160). viaAlias reads typeof m.logger.bind(null) where m is const m = my (test/bundler/bundler_drop.test.ts:148-150). Because m is a bound local, e_identifier's Unbound check at src/js_parser/visit/visit_expr.rs:256 never sets method_call_must_be_replaced_with_undefined, so e_call never reaches the undefined arm or the new bind arm. At run time my.logger is the real function from globalThis.my = { logger() {...} }, so typeof is "function" on the base branch and on this PR alike. The comment at test/bundler/bundler_drop.test.ts:143 says single-use locals are "inlined and visited again"; substitute_single_use_symbol_in_stmt (src/js_parser/visit/mod.rs:1963, src/js_parser/p.rs:2787) rewrites the already-visited AST and does not re-run the visitor, so the drop logic never sees the inlined form. The other rows in the same test (viaLocal, the bound calls) do fail on the base, so the test as a whole is live; only this row and its comment are vacuous.

Verification: The viaAlias row prints "callable" regardless of whether the .bind fix is present. m is a block-scoped const, so it is a bound symbol; src/js_parser/visit/visit_expr.rs:256-297 is gated on Kind::Unbound, so m.logger.bind(null) never reaches the drop branch of e_call and is emitted unchanged. The comment at test line 153 is wrong: nothing is visited again.

Comment on lines +467 to +469
// Transpiled on the first run, served from the cache on the second.
expect(run(temp_dir, ["--drop=console", "a.js"])).toBe("function");
expect(run(temp_dir, ["--drop=console", "a.js"])).toBe("function");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 nit (optional): the new cache test cannot tell a cache hit from a re-transpile, so the EXPECTED_VERSION bump it accompanies is not exercised. The comment at test/cli/run/transpiler-cache.test.ts:467 says the second run is served from the cache, but no newCacheCount() assertion backs it, unlike the sibling test at test/cli/run/transpiler-cache.test.ts:445-455. Fix: assert newCacheCount() after each run (1 after the first write, 0 after the --drop=console runs) so the test follows the file's convention and the cache-served claim can fail.

Why this was flagged

The test at test/cli/run/transpiler-cache.test.ts:458-470 runs the file three times and only compares stdout. Nothing in it reads the cache directory, so the test passes identically whether the second --drop=console run reads the cached entry or transpiles again, and it passes if the transpiler cache were disabled entirely. Every neighbouring test in this describe block (for example test/cli/run/transpiler-cache.test.ts:446, :449, :452, :455) pairs each run with expect(newCacheCount()).toBe(...). The comment on line 467 therefore states something the test does not verify. Consequence is test-quality only: no runtime behaviour differs from the base branch.

Verification: nit. Triggering condition: whenever the new test runs, it exercises only stdout, so it passes with the on-disk cache engaged, bypassed, or disabled. At test/cli/run/transpiler-cache.test.ts:458-470 the three run(...) calls are wrapped in expect(...).toBe(...) on stdout only; nothing in the test reads cache_dir (no newCacheCount()), so the comment at :467 is not backed by any assertion.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants